#include "SCGI.h"
#include "Server.h"
#include "Util.h"

using namespace scgi;

Parser::Parser() : state(0), length(0), current(0), error(0) { }

int Parser::push(char * & first_, char * & last_)
{
	char ch;
	switch(state)
	{
s0: case 0: state = 0; if(first_ == last_) goto sEND;
		ch = *first_++;
		if('0' <= ch && ch <= '9')
		{
			length = length * 10 + ch - '0';
			goto s0;
		}
		else if(ch == ':') goto s2;
		else goto sERROR;
case 1:
		key.clear();
		value.clear();
s2: case 2: state = 2; if(first_ == last_) goto sEND;
		if(length < current) goto sERROR;
		ch = *first_++; ++current;
		if(ch == 0) goto s3;
		else
		{
			key.append(1, ch);
			goto s2;
		}
s3: case 3: state = 3; if(first_ == last_) goto sEND;
		ch = *first_++; ++current;
		if(length == current) goto s4;
		if(ch == 0)
		{
			state = 1;
			return 0;
		}
		else
		{
			value.append(1, ch);
			goto s3;
		}
s4: case 4: state = 4; if(first_ == last_) goto sEND;
		if(*first_++ != ',') goto sERROR;
		state = 5;
		goto sEND;
sERROR:
		error = 1;
		return -1;
sEND:;
	}
	return 1;
}

Parser::pointer Parser::create()
{
	return Parser::pointer(new Parser);
}

void RequestPath::parse(std::string input_)
{
	path.clear();
	file.clear();
	query.clear();

	std::string::const_iterator
		  it1 = input_.begin()
		, it2 = input_.end();
	std::string current;
	char ch;

s0: // path and file.
	if(it1 == it2)
	{
		file.append(current);
		return;
	}
	ch = *it1++;
	if(ch == '?')
	{
		file.append(current);
		current.clear();
		goto s1;
	}
	if(ch == '/')
	{
		current.append(1, ch);
		path.append(current);
		current.clear();
		goto s0;
	}
	current.append(1, ch);
	goto s0;
s1: // query.
	if(it1 == it2)
	{
		query.append(current);
		return;
	}
	current.append(1, *it1++);
	goto s1;
}

Request::Request(Server & server_)
	: server(server_)
	, socket(server_.service)
	, buffer(new char[buffer_size])
	, buffer_size(server_.buffer_size) { }

void Request::start()
{
	read(Parser::create());
}

void Request::read(Parser::pointer parser_)
{
	Request::pointer obj = shared_from_this();

	socket.async_read_some(
		  boost::asio::buffer(buffer.get(), buffer_size)
		, [=](boost::system::error_code const & error_, size_t length_)
		{
			if(!error_)
			{
				obj->first = obj->buffer.get();
				obj->last = obj->first + length_;
				while(obj->first != obj->last)
				{
					if(parser_->push(obj->first, obj->last)) break;
					obj->env[parser_->key] = parser_->value;
				}

				if(parser_->error) { obj->error(400); }
				else if(parser_->state == 5) { obj->start_application(); }
				else { obj->read(parser_); }
			}
			else { obj->error(400); }
		});
}

void Request::start_application()
{
	std::string const & http_cookie = env["HTTP_COOKIE"];
	cookie.assign(http_cookie.begin(), http_cookie.end());

	content_length = to_uint_32(env["CONTENT_LENGTH"]);

	Application::pointer application = server.create(shared_from_this());
	if(!application)
	{
		error(404);
		return;
	}

	application->assign(shared_from_this());
	application->start();
	read(application, content_length);
}

void Request::read(Application::pointer application_, size_t remainings_)
{
	if(first != last)
	{
		application_->push(first, last);
		remainings_ -= last - first;
		first = last;
	}
	assert(0 <= remainings_);

	if(remainings_ <= 0)
	{
		application_->eos();
		return;
	}

	Request::pointer obj = shared_from_this();

	socket.async_read_some(
		  boost::asio::buffer(buffer.get(), buffer_size)
		, [=](boost::system::error_code const & error_, size_t length_)
		{
			obj->first = obj->buffer.get();
			obj->last = obj->first + length_;
			obj->read(application_, remainings_);
		});
}

void Request::close_socket()
{
	socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
	socket.close();
}

void Request::error(unsigned short code_)
{
	Request::pointer obj = shared_from_this();
	std::shared_ptr<std::string> message(new std::string);
	
	if(code_ == 400) { message->assign("Status: 400 Bad Request\r\n\r\n"); }
	else if(code_ == 404) { message->assign("Status: 404 Not Found\r\n\r\n"); }
	else if(code_ == 503) { message->assign("Status: 503 Service Unavailable\r\n\r\n"); }
	else { message->assign("Status: 500 Internal Server Error\r\n\r\n"); }

	boost::asio::async_write(
		  socket
		, boost::asio::buffer(*message)
		, boost::asio::transfer_all()
		, [=](boost::system::error_code const & error_, size_t)
		{
			obj;
			message;
		});
}

Request::pointer Request::create(Server & server_)
{
	return Request::pointer(new Request(server_));
}

Listener::Listener(Server & server_) : server(server_) { }
	
void Listener::start() { accept(); }

Application::~Application() { }

Time Application::now()
{
	return request->server.clock.now();
}

void Application::close_socket() { request->close_socket(); }

Session::pointer Application::create_session(Time expires_)
{
	return request->server.session_table.create(expires_);
}

Session::pointer Application::get_session()
{
	return request->server.session_table.get(
		to_uint_64(request->cookie.get("sid")));
}

void Application::set_session(Session::pointer session_)
{
	request->server.session_table.set(session_);
}

size_t Application::get_cached_session_size() const
{
	return request->server.session_table.get_cache_size();
}

void Application::assign(Request::pointer request_) { request = request_; }

void Application::start() { }

void Application::push(char * first_, char * last_) { }

void Application::eos() { }

void Application::error(unsigned short code_) { request->error(code_); }


Factory::~Factory() { }